/*----------------------------------------------------------------------------
 * Copyright (c) <2018>, <Huawei Technologies Co., Ltd>
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *---------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 *---------------------------------------------------------------------------*/
/**
 *  DATE                AUTHOR      INSTRUCTION
 *  2019-08-5 15:09    yuhengP     The first version
 *
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>

//#include <sys/types.h>
//#include <sys/socket.h>
//#include <netdb.h>

#include <sal_imp.h>
#include <sal_define.h>
#include <sal_types.h>
#include <esp8266_socket_imp.h>
#include <at.h>
#include <link_misc.h>
#include <link_endian.h>
#include <osal.h>

#define cn_esp8266_cmd_timeout   (5*1000)
#define cn_esp8266_rcvindex      "\r\n+IPD"
#define cn_esp8266_cachelen      (1024 * 2)

typedef struct
{
    int sockfd;
    int domain;
    int type;
    int protocol;

    tag_ring_buffer_t esp8266_rcvring;
    unsigned char esp8266_rcvbuf[cn_esp8266_cachelen];

    unsigned int timeout;

    char oob_resp[1024];
}esp8266_sock_cb_t;

static esp8266_sock_cb_t s_esp8266_sock_cb;

//esp8266 common at command
static bool_t esp8266_atcmd(const char *cmd,const char *index)
{
    int ret = 0;
    ret = at_command((unsigned char *)cmd,strlen(cmd),index,NULL,0,cn_esp8266_cmd_timeout);
    if(ret >= 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

//at command  with response
static bool_t esp8266_atcmd_response(const char *cmd,const char *index,char *buf, int len)
{
    int ret = 0;
    ret = at_command((unsigned char *)cmd,strlen(cmd),index,(char *)buf,len,cn_esp8266_cmd_timeout);
    if(ret >= 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

static int esp8266_rcvdeal(void *args,void *msg,size_t len)
{
    int ret = 0;
    int fd;
    unsigned short datalen;
    char *data;
    char  *str;
    unsigned short ringspace;

    data = msg;
    if(len <strlen(cn_esp8266_rcvindex))
    {
        printf("%s:invalid frame:%d byte:%s\n\r",__FUNCTION__,len,(char *)data);
        return ret;
    }
    //now deal the data
    str = strstr((char *)data,",");
    if(NULL == str)
    {
        printf("%s:format error\n\r",__FUNCTION__);
        return ret; //format error
    }
    str++;

    //TODO: mux = 1
    fd = 0;

    datalen = 0;
    for (; *str <= '9' && *str >= '0' ;str++)
    {
        datalen = (datalen * 10 + (*str - '0'));
    }
    str++;

    //now this is data payload
    if(datalen > (cn_esp8266_cachelen - ring_datalen(&s_esp8266_sock_cb.esp8266_rcvring)))
    {
    	return 0;
    }

    if(s_esp8266_sock_cb.type == SOCK_DGRAM)
    {
    	ret = ring_write(&s_esp8266_sock_cb.esp8266_rcvring,(unsigned char *)&datalen,sizeof(datalen));
        ret = ring_write(&s_esp8266_sock_cb.esp8266_rcvring,str,datalen);
    }
    else if (s_esp8266_sock_cb.type == SOCK_STREAM)
    {
        ret = ring_write(&s_esp8266_sock_cb.esp8266_rcvring,str,datalen);
    }

    return ret;

}

static int esp8266_socket(int domain, int type, int protocol)
{
	s_esp8266_sock_cb.domain = domain;
	s_esp8266_sock_cb.type = type;
	s_esp8266_sock_cb.protocol = protocol;
	s_esp8266_sock_cb.sockfd = 0;

	return s_esp8266_sock_cb.sockfd;
}

static int __esp8266_bind(int fd, const struct sockaddr *addr, int addrlen)
{
	//not implemented for client
    return 0;
}

static int esp8266_listen(int fd, int backlog)
{
	//not implemented for client
    return 0;
}

static int __esp8266_accept(int fd, struct sockaddr *addr, int addrlen)
{
	//not implemented for client
    return 0;
}

static int __esp8266_connect(int fd, const struct sockaddr *addr, int addrlen)
{
    char cmd[64];
    int ret = -1;
    char *remote_ip;

    if(NULL != addr)
    {
        memset(cmd,0,64);
        //memset(remote_ip,0,16);

        const struct sockaddr_in *serv_addr = (const struct sockaddr *)addr;
        uint16_t remote_port = ntohs(serv_addr->sin_port);
        struct in_addr remote_ip_int = serv_addr->sin_addr;
        remote_ip = inet_ntoa(remote_ip_int);

        //TODO: mux = 1
        if(s_esp8266_sock_cb.type == SOCK_DGRAM)
        {
        	snprintf(cmd,64,"AT+CIPSTART=\"UDP\",\"%s\",%d\r\n",remote_ip,remote_port);
        }
        else if(s_esp8266_sock_cb.type == SOCK_STREAM)
        {
        	snprintf(cmd,64,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",remote_ip,remote_port);
        }
        else return ret;

        if(esp8266_atcmd(cmd,"OK"))
        {
        	ret = 0;
        }
    }
    return ret;
}

static int esp8266_send(int fd, const void *buf, int len, int flags)
{
	char cmd[64];
    int ret = -1;

    if(NULL != buf)
    {
        memset(cmd,0,64);
        memset(s_esp8266_sock_cb.oob_resp,0,1024);

        snprintf(cmd,64,"AT+CIPSEND=%d\r\n",len); //TODO:mux = 1

        if(esp8266_atcmd(cmd,">"))
        {
        	ret = at_command((unsigned char *)buf,len,"SEND OK",(char *)s_esp8266_sock_cb.oob_resp,1024,cn_esp8266_cmd_timeout);
        	printf("ret = %d\r\n",ret);
        	char *str;
        	str = strstr(s_esp8266_sock_cb.oob_resp,cn_esp8266_rcvindex);  //in some cases, +IPD is not at the beginning of one frame. process here
        	if(NULL != str)
        	{
        		void *args;
        		esp8266_rcvdeal(args,str,1024);
        	}

        }
    }
    if(ret > 0)
    {
    	ret = len;
    }
    return ret;
}
static int __esp8266_sendto(int fd, const void *msg, int len, int flags, struct sockaddr *addr, int addrlen)
{
    return esp8266_send(fd,msg,len,flags);
}

static int esp8266_recv(int fd,void *buf,size_t len,int flags)
{
	int ret = -1;
	osal_loop_timer_t sock_timer;

	osal_loop_timer_init(&sock_timer);
	osal_loop_timer_count_downms(&sock_timer,s_esp8266_sock_cb.timeout);

    while (0 == osal_loop_timer_expired(&sock_timer))
    {
    	if(s_esp8266_sock_cb.type == SOCK_DGRAM)
    	{
    		unsigned short framelen = 0;
    		ret = ring_read(&s_esp8266_sock_cb.esp8266_rcvring,(unsigned char *)&framelen,sizeof(framelen));
    		ret = ring_read(&s_esp8266_sock_cb.esp8266_rcvring,(unsigned char *)buf,framelen);
    		if(ret > 0)
    		{
    			break;
    		}
    	}
    	else if (s_esp8266_sock_cb.type == SOCK_STREAM)
    	{
    		ret = ring_read(&s_esp8266_sock_cb.esp8266_rcvring,(unsigned char *)buf,len);
    		if(ret > 0)
    		{
    		    break;
    		}
    	}
    	osal_task_sleep(1);
    }


	if(0 == ret)
	{
		return -1;
	}
	else return ret;
}

static int __esp8266_recvfrom(int fd, void *msg, int len, int flags, struct sockaddr *addr, socklen_t *addrlen)
{
    return esp8266_recv(fd,msg,len,flags);
}


static int __esp8266_setsockopt(int fd, int level, int option, const void *option_value, int option_len)
{
	//TODO:MUX=1
	struct timeval *timedelay = (struct timeval *)option_value;
	s_esp8266_sock_cb.timeout = (timedelay->tv_sec * 1000) + (timedelay->tv_usec / 1000);
    return 0;
}

static int esp8266_getsockopt(int fd, int level, int optname, const void *optval, int optlen)
{
	//not implemented
    return 0;
}

static int esp8266_close(int fd)
{
	char cmd[64];
	int ret = -1;
	memset(cmd,0,64);
	snprintf(cmd,64,"AT+CIPCLOSE\r\n");//TODO: MUX = 1;
	if(esp8266_atcmd(cmd,"OK"))
	{
    	ret = 0;
    }
	return ret;
}

static int esp8266_shutdown(int fd, int how)
{
	return esp8266_close(fd);
}

static int esp8266_getsockname(int fd, struct sockaddr *addr,socklen_t *addrlen)
{
    //not implemented
    return 0;
}

static int esp8266_getpeername(int fd, struct sockaddr *addr,socklen_t *addrlen)
{
    //not implemented
    return 0;
}

static struct hostent *esp8266_gethostbyname(const char *name)
{
	char cmd[64];
	char resp[64];

	char *str;
	struct in_addr *serv_ip;
	int ret = -1;
	int cpylen;
	struct hostent *hptr = (struct hostent *)osal_malloc(sizeof(struct hostent));
	hptr->h_addr_list = (char **)osal_malloc(sizeof(char *) * 1);
	hptr->h_addr_list[0] = (char *)osal_malloc(sizeof(char) * 4);

	hptr->h_addrtype = AF_INET;
	memset(cmd,0,64);
	snprintf(cmd,64,"AT+CIPDOMAIN=\"%s\"\r\n",name);
	if(false == esp8266_atcmd_response(cmd,"+CIPDOMAIN",resp,64))
	{
		//name is ip address already
		sscanf(name,"%d.%d.%d.%d",&hptr->h_addr_list[0][0],&hptr->h_addr_list[0][1],&hptr->h_addr_list[0][2],&hptr->h_addr_list[0][3]);
		return hptr;
	}
	str = strstr(resp,":");
	str++;
	cpylen = strstr(str,"\r\n") - str;

	char ipaddr[cpylen];
	memset(ipaddr,0,cpylen);
	memcpy(ipaddr,str,cpylen);
	sscanf(ipaddr,"%d.%d.%d.%d",&hptr->h_addr_list[0][0],&hptr->h_addr_list[0][1],&hptr->h_addr_list[0][2],&hptr->h_addr_list[0][3]);

    return hptr;
}

static const tag_tcpip_ops s_tcpip_socket_ops =
{
   .socket = (fn_sal_socket)esp8266_socket,
   .bind = (fn_sal_bind)__esp8266_bind,
   .listen = (fn_sal_listen)esp8266_listen,
   .connect = (fn_sal_connect)__esp8266_connect,
   .accept = (fn_sal_accept)__esp8266_accept,
   .send = (fn_sal_send)esp8266_send,
   .sendto = (fn_sal_sendto)__esp8266_sendto,
   .recv = (fn_sal_recv)esp8266_recv,
   .recvfrom = (fn_sal_recvfrom)__esp8266_recvfrom,
   .setsockopt = (fn_sal_setsockopt)__esp8266_setsockopt,
   .getsockopt = (fn_sal_getsockopt)esp8266_getsockopt,
   .shutdown =(fn_sal_shutdown)esp8266_shutdown,
   .closesocket =(fn_sal_closesocket)esp8266_close,
   .getpeername =(fn_sal_getpeername)esp8266_getpeername,
   .getsockname = (fn_sal_getsockname)esp8266_getsockname,
   .gethostbyname = (fn_sal_gethostbyname)esp8266_gethostbyname,
};

static const tag_tcpip_domain s_tcpip_socket =
{
    .name = "esp8266 socket",
    .domain = AF_INET,
    .ops = &s_tcpip_socket_ops,
};


int tcpipstack_install_esp8266_socket(void)
{
    int ret = -1;

    ret = tcpip_sal_install(&s_tcpip_socket);

    if(0 == ret)
    {
        printf("sal:install socket success\r\n");
    }
    else
    {
        printf("sal:install socket failed\r\n");
    }

    return 0;
}

static bool_t esp8266_reset(void)
{
    return esp8266_atcmd("AT+RST\r\n","ready");
}

static bool_t esp8266_echo_off(void)
{
    return esp8266_atcmd("ATE0\r\n","OK");
}

static bool_t esp8266_show_dinfo(int flag)
{
	char cmd[64];
	memset(cmd,0,64);
	snprintf(cmd,64,"AT+CIPDINFO=%d\r\n",flag);
    return esp8266_atcmd(cmd,"OK");
}

static bool_t esp8266_set_mode(enum_net_mode mode)
{
	char cmd[64];
	memset(cmd,0,64);
	snprintf(cmd,64,"AT+CWMODE_CUR=%d\r\n",(int)mode);
	return esp8266_atcmd(cmd,"OK");
}

static bool_t esp8266_joinap(char *ssid, char *passwd)
{
	char cmd[64];
	memset(cmd,0,64);
    snprintf(cmd,64,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",ssid, passwd);
    return esp8266_atcmd(cmd,"OK");
}

static bool_t esp8266_set_mux(int mux)
{
	char cmd[64];
	memset(cmd,0,64);
    snprintf(cmd,64,"AT+CIPMUX=%d\r\n",mux);
    return esp8266_atcmd(cmd,"OK");
}

int esp8266_boot(void)
{
    at_oobregister("esp8266rcv",cn_esp8266_rcvindex,strlen(cn_esp8266_rcvindex),esp8266_rcvdeal,NULL);
    ring_init(&s_esp8266_sock_cb.esp8266_rcvring,s_esp8266_sock_cb.esp8266_rcvbuf,cn_esp8266_cachelen,0,0);


    esp8266_reset();
    esp8266_echo_off();
    esp8266_show_dinfo(0);
    esp8266_set_mode(STA);
    esp8266_set_mux(0);

    while(false == esp8266_joinap(WIFI_SSID, WIFI_PASSWD))
    {
        printf("connect ap failed, repeat...\r\n");
    }
   //reach here means everything is ok, we can go now

    return 0;
}



